package com.demo.gulimall.search.service.impl;

import com.alibaba.fastjson.JSON;
import com.demo.gulimall.common.to.es.ProductEsTo;
import com.demo.gulimall.search.config.MallElasticSearchConfig;
import com.demo.gulimall.search.constant.EsConstant;
import com.demo.gulimall.search.service.MallSearchService;
import com.demo.gulimall.search.vo.SearchParam;
import com.demo.gulimall.search.vo.SearchResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.*;
import org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.*;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 检索服务
 * @author Chen Yuan
 * @create 2023-06-07  14:06
 */
@Slf4j
@Service
public class MallSearchServiceImpl implements MallSearchService {

	private static final String priceBandSpe = "_";
	private static final String attrValueSpe = ":";

	@Autowired
	private RestHighLevelClient restHighLevelClient;

	@Override
	public SearchResult search(SearchParam searchParam) {
		SearchResult searchResult = new SearchResult();

		// 1. 动态生成检索 DSL 语句
		SearchSourceBuilder searchSourceBuilder = this.buildSearchSourceBuilder(searchParam);
		//System.out.println(searchSourceBuilder.toString());

		// 2. 创建检索请求
		SearchRequest searchRequest = new SearchRequest(new String[]{EsConstant.PRODUCT_INDEX}, searchSourceBuilder);

		try {
			// 3. 执行请求
			SearchResponse searchResponse = restHighLevelClient.search(searchRequest, MallElasticSearchConfig.COMMON_OPTIONS);

			// 4. 解析检索结果
			searchResult = this.buildSearchResult(searchParam, searchResponse);
		} catch (IOException e) {
			log.info("--> search - error - msg:", e);
		}

		return searchResult;
	}

	/**
	 * 构建 DSL 语句
	 */
	private SearchSourceBuilder buildSearchSourceBuilder(SearchParam searchParam) {
		SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

	// 模糊匹配、过滤
		BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
		//      匹配检索关键字
		if (StringUtils.isNotEmpty(searchParam.getKeyword())) {
			boolQuery.must(QueryBuilders.matchQuery("skuTitle", searchParam.getKeyword()));
		}
		//      过滤三级分类
		if (null != searchParam.getCatalog3Id()) {
			boolQuery.filter(QueryBuilders.termQuery("catalogId", searchParam.getCatalog3Id()));
		}
		//      过滤是否有库存
		if (null != searchParam.getHasStock()) {
			boolQuery.filter(QueryBuilders.termQuery("hasStock", (Objects.equals(searchParam.getHasStock(), 1))));
		}
		//      过滤品牌
		if (null != searchParam.getBrandIds() && searchParam.getBrandIds().size() > 0) {
			boolQuery.filter(QueryBuilders.termsQuery("brandId", searchParam.getBrandIds()));
		}
		//      过滤价格区间
		if (null != searchParam.getSkuPriceBand() && searchParam.getSkuPriceBand().contains(priceBandSpe)) {
			RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("skuPrice");

			String[] priceList = searchParam.getSkuPriceBand().split(priceBandSpe);
			if (searchParam.getSkuPriceBand().endsWith(priceBandSpe) || priceList.length == 2) {
				// 有起始, 则获取价格区间的第一个值
				rangeQuery.gte(priceList[0]);
			}
			if (searchParam.getSkuPriceBand().startsWith(priceBandSpe) || priceList.length == 2) {
				// 有截至, 则获取价格区间的最后一个值
				rangeQuery.lte(priceList[priceList.length-1]);
			}
			boolQuery.filter(rangeQuery);
		}
		//      过滤属性
		if (null != searchParam.getAttrs() && searchParam.getAttrs().size() > 0){
			String[] split, attrValues;
			String attrId;
			for (String attrStr : searchParam.getAttrs()) {
				BoolQueryBuilder nestedBoolQuery = QueryBuilders.boolQuery();
				// 参数格式 attr=1_5寸:8存
				split = attrStr.split(priceBandSpe);
				attrId = split[0];
				attrValues = split[1].split(attrValueSpe);
				nestedBoolQuery.must(QueryBuilders.termQuery("attrs.attrId", attrId))
								.must(QueryBuilders.termsQuery("attrs.attrValue.keyword", attrValues));
				// 每一个属性都必须构建一个过滤查询
				NestedQueryBuilder nestedQuery = QueryBuilders.nestedQuery("attrs", nestedBoolQuery, ScoreMode.None);
				boolQuery.filter(nestedQuery);
			}
		}

		sourceBuilder.query(boolQuery);

	// 排序、分页、高亮
		// 排序
		if (StringUtils.isNotEmpty(searchParam.getSort())) {
			// 排序格式, sort=saleCount_asc/desc
			String sort = searchParam.getSort();
			String[] sortValues = sort.split(priceBandSpe);
			SortOrder order = SortOrder.ASC;
			if (sortValues.length == 2 && Objects.equals("desc", sortValues[1])){
				order = SortOrder.DESC;
			}
			sourceBuilder.sort(sortValues[0], order);
		}
		// 分页
		/*
			from 字段需要按照当前页进行计算
			currentPage:1   from:0  size:5  ->  [0,1,2,3,4]
			currentPage:2   from:5  size:5  ->  [5,6,7,8,9]
			……
			from = (currentPage - 1) * pageSize
		 */
		int pageSize = EsConstant.PAGE_SIZE;
		sourceBuilder.from((searchParam.getCurrentPage() - 1) * pageSize);
		sourceBuilder.size(pageSize);
		// 高亮
		if (StringUtils.isNotEmpty(searchParam.getKeyword())){
			HighlightBuilder highlightBuilder = new HighlightBuilder();
			highlightBuilder.field("skuTitle")
							.preTags("<b style='color:red'>")
							.postTags("</b>");
			sourceBuilder.highlighter(highlightBuilder);
		}


	// 聚合分析
		// 品牌聚合
		TermsAggregationBuilder brandAgg = AggregationBuilders.terms("brand_agg").field("brandId").size(100);
		//      子聚合, 聚合品牌名、图片
		brandAgg.subAggregation(AggregationBuilders.terms("brand_name_agg").field("brandName").size(1));
		brandAgg.subAggregation(AggregationBuilders.terms("brand_img_agg").field("brandImg").size(1));
		sourceBuilder.aggregation(brandAgg);

		// 分类聚合
		TermsAggregationBuilder catalogAgg = AggregationBuilders.terms("catalog_agg").field("catalogId").size(100);
		catalogAgg.subAggregation(AggregationBuilders.terms("catalog_name").field("catalogName").size(1));
		sourceBuilder.aggregation(catalogAgg);

		// 属性聚合
		NestedAggregationBuilder nestedAgg = AggregationBuilders.nested("attr_agg", "attrs");
		TermsAggregationBuilder attrIdAgg = AggregationBuilders.terms("attr_id_agg").field("attrs.attrId").size(100);
		attrIdAgg.subAggregation(AggregationBuilders.terms("attr_name_agg").field("attrs.attrName").size(100));
		attrIdAgg.subAggregation(AggregationBuilders.terms("attr_values_agg").field("attrs.attrValue.keyword").size(100));
		nestedAgg.subAggregation(attrIdAgg);
		sourceBuilder.aggregation(nestedAgg);

		return sourceBuilder;
	}

	/**
	 * 解析检索结果
	 */
	private SearchResult buildSearchResult(SearchParam searchParam, SearchResponse searchResponse) {
		SearchResult result = new SearchResult();

		SearchHits hits = searchResponse.getHits();
		Aggregations aggregations = searchResponse.getAggregations();

	// 商品信息
		SearchHit[] hitList = hits.getHits();
		if (hitList != null && hitList.length > 0) {

			List<ProductEsTo> products = Arrays.stream(hitList).map(hit -> {
				ProductEsTo productTo = JSON.parseObject(hit.getSourceAsString(), ProductEsTo.class);
				if (StringUtils.isNotEmpty(searchParam.getKeyword())) {
					HighlightField skuTitle = hit.getHighlightFields().get("skuTitle");
					productTo.setSkuTitle(skuTitle.getFragments()[0].string());
				}
				return productTo;
			}).collect(Collectors.toList());

			result.setProducts(products);
		}

		List<? extends Terms.Bucket> buckets;
	// 商品列表所涉及的属性信息
		ParsedNested attrAgg = aggregations.get("attr_agg");
		ParsedLongTerms attrIdAgg = attrAgg.getAggregations().get("attr_id_agg");
		buckets = attrIdAgg.getBuckets();
		List<SearchResult.AttrVo> attrVos = new ArrayList<>();
		for (Terms.Bucket bucket : buckets) {
			SearchResult.AttrVo attrVo = new SearchResult.AttrVo();

			attrVo.setAttrId(Long.parseLong(bucket.getKeyAsString()));
			attrVo.setAttrName(((ParsedStringTerms)bucket.getAggregations().get("attr_name_agg")).getBuckets().get(0).getKeyAsString());
			attrVo.setAttrValues(((ParsedStringTerms)bucket.getAggregations().get("attr_values_agg")).getBuckets().stream().map(MultiBucketsAggregation.Bucket::getKeyAsString).collect(Collectors.toList()));

			attrVos.add(attrVo);
		}
		result.setAttrs(attrVos);

	// 商品列表所涉及的品牌信息
		ParsedLongTerms brandAgg = aggregations.get("brand_agg");
		buckets = brandAgg.getBuckets();
		List<SearchResult.BrandVo> brandVos = new ArrayList<>();
		for (Terms.Bucket bucket : buckets) {
			SearchResult.BrandVo brandVo = new SearchResult.BrandVo();

			brandVo.setBrandId(Long.parseLong(bucket.getKeyAsString()));
			brandVo.setBrandName(((ParsedStringTerms)bucket.getAggregations().get("brand_name_agg")).getBuckets().get(0).getKeyAsString());
			brandVo.setBrandImg(((ParsedStringTerms)bucket.getAggregations().get("brand_img_agg")).getBuckets().get(0).getKeyAsString());

			brandVos.add(brandVo);
		}
		result.setBrands(brandVos);

	// 商品列表所涉及的分类信息
		ParsedLongTerms catalogAgg = aggregations.get("catalog_agg");
		buckets = catalogAgg.getBuckets();
		List<SearchResult.CatalogVo> catalogVos = new ArrayList<>();
		for (Terms.Bucket bucket : buckets) {
			SearchResult.CatalogVo catalogVo = new SearchResult.CatalogVo();

			catalogVo.setCatalogId(Long.parseLong(bucket.getKeyAsString()));
			catalogVo.setCatalogName(((ParsedStringTerms)bucket.getAggregations().get("catalog_name")).getBuckets().get(0).getKeyAsString());

			catalogVos.add(catalogVo);
		}
		result.setCatalogs(catalogVos);

	// 分页信息
		// 总记录数
		long total = hits.getTotalHits().value;
		result.setTotal(total);
		// 页面, 需要计算
		result.setTotalPages(((int)total % EsConstant.PAGE_SIZE == 0 ? 0 : 1) + (int)total / EsConstant.PAGE_SIZE);
		// 当前页
		result.setCurrentPage(searchParam.getCurrentPage());

		return result;
	}
}
